package doc.jmidifile;


import java.io.*;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;

public class JMidiFile implements Serializable {

    public static final int DEFAULT_TPQ = 120;
    private static final int[] DEFAULT_TIME_SIGN = new int[]{4, 4};

    public final int tpq;
    private List<JMidiTrack> tracks = new ArrayList<>();

    /**
     * 打开 midi 文件进行读取
     */
    public JMidiFile(File file) throws FileNotFoundException, IOException, InvalidParameterException {
        this(new FileInputStream(file));
    }

    public JMidiFile(InputStream stream) throws IOException, InvalidParameterException {
        // 获取 midi file data
        BufferedInputStream in = new BufferedInputStream(stream);
        byte[] data = new byte[in.available()];
        in.read(data);
        int[] off = new int[]{0};

        // 读取文件头
        JMidiHead head = new JMidiHead(data, off);
        // 读取每个音轨的数据
        for (int i = 0; i < head.trackCount; ++i) {
            tracks.add(new JMidiTrack(data, off));
        }

        // 设置 tpq
        this.tpq = head.tpq;

        // 计算 event 的时间
        setEventTime();
    }

    /**
     * 创建文件，用于写入
     */
    public JMidiFile() {
        this(DEFAULT_TPQ);
    }

    public JMidiFile(int tpq) throws InvalidParameterException {
        if (tpq < 48 || (tpq % 12) != 0) {
            throw new InvalidParameterException("TPQ must be a multiple of 12 and bigger or equal 48");
        }
        this.tpq = tpq;
    }

    public void save(String filePath) throws IOException {
        int trackCount = tracks.size();
        if (trackCount == 0) {
            throw new IOException("empty midi data");
        }
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(filePath));

        // 写文件头
        int format = (trackCount == 1) ? JMidiHead.SINGLE_TRACK_FORMAT : JMidiHead.COMMON_META_TRACK_FORMAT;
        JMidiHead head = new JMidiHead(format, trackCount, this.tpq);
        head.save(out);

        // 写音轨数据
        setEventTick();
        for (JMidiTrack track : tracks) {
            track.save(out);
        }
        out.flush();
    }

    /**
     * 获取声轨数量
     */
    public int getTrackCount() {
        return tracks.size();
    }

    /**
     * 获取指定的音轨数据
     *
     * @param tid 音轨索引
     * @return null tid 无效
     */
    public JMidiTrack getTrack(int tid) throws InvalidParameterException {
        if (tid < 0 || tid >= tracks.size()) {
            makeInvalidTrackIdException();
        }
        return tracks.get(tid);
    }

    public void addTrack(JMidiTrack track) throws InvalidParameterException {
        if (track.events.isEmpty()) {
            throw new InvalidParameterException("empty track can't be added");
        }
        tracks.add(track);
    }

    /**
     * 获取某个声轨的时长
     *
     * @param tid 声轨索引。如果 tid < 0 则获取整个文件的时长
     * @return < 0 参数错误
     */
    public long getDuration(int tid) throws InvalidParameterException {
        if (tracks.isEmpty()) return 0;

        if (tid >= tracks.size()) {
            makeInvalidTrackIdException();
        }
        long duration = -1;
        if (tid < 0) {
            for (JMidiTrack track : tracks) {
                duration = Math.max(duration, track.getDuration());
            }
            return duration;
        }
        return tracks.get(tid).getDuration();
    }

    static private void makeInvalidTrackIdException() throws InvalidParameterException {
        throw new InvalidParameterException("invalid track id");
    }

    private void setEventTime() {
        if (tracks.isEmpty()) return;
        // 生成 tempo map
        JMidiTempoMap tempoMap = new JMidiTempoMap(tracks.get(0), tpq);
        // 利用 tempo map 计算所有数据的时间
        for (JMidiTrack track : tracks) {
            for (JMidiEvent e : track.events) {
                e.ms = tempoMap.ticksToMs(e.tick);
            }
        }
    }

    private void setEventTick() {
        if (tracks.isEmpty()) return;
        // 生成 tempo map
        JMidiTempoMap tempoMap = new JMidiTempoMap(tracks.get(0), tpq);
        // 利用 tempo map 计算所有 event 的 tick
        for (JMidiTrack track : tracks) {
            for (JMidiEvent e : track.events) {
                e.tick = tempoMap.msToTicks(e.ms);
            }
        }
    }
}
